package com.google.code.xconf.schema.definition;

import com.google.code.xconf.Property;
import com.google.code.xconf.PropertyGroup;
import com.google.code.xconf.schema.SchemaSettings;
import com.google.code.xconf.schema.SchemaDefinition;
import com.google.code.xconf.schema.SchemaValidationException;
import static com.google.code.xconf.schema.SchemaConstants.*;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class PropertyValuesDefinition extends SchemaDefinition {

    private int minValueDefinitions;
    private int maxValueDefinitions;
    private List<PropertyValueDefinition> propertyValueDefinitions = new ArrayList<>();

    public PropertyValuesDefinition(PropertyGroup delegate, SchemaSettings schemaSettings) {
        super(PROPERTY_VALUES, delegate, schemaSettings);
        configure();
    }

    private void configure() {
        settings = settings.snapshot();
        settings.setPropertyValueCaseSensitive(resolvePropertyValueCaseSensitive());
        propertyValueDefinitions = resolvePropertyValueDefinitions();
        minValueDefinitions = resolveMinNumValueDefinitions();
        maxValueDefinitions = resolveMaxNumValueDefinitions();
    }

    @Override
    protected List<String> getValidNodeNames() {
        return Arrays.asList(PROPERTY_VALUE_CASE_SENSITIVE, PROPERTY_VALUE);
    }

    public void validate(Property property) throws SchemaValidationException {
        List<String> values = property.getValues();
        if (values.size() < minValueDefinitions || values.size() > maxValueDefinitions) {
            throw new SchemaValidationException("Values " + values + " in property '" + property.getName() + "' do not match number of required values (" + minValueDefinitions + "-" + maxValueDefinitions + ")");
        }
        if (!propertyValueDefinitions.isEmpty()) {
            int j = 0;
            for (int i = 0; i < values.size(); i++) {
                String value = values.get(i);
                if (j == propertyValueDefinitions.size()) {
                    throw new SchemaValidationException("Value '" + value + "' in property '" + property.getName() + "'does not match any of the expected option(s)");
                }
                PropertyValueDefinition def = propertyValueDefinitions.get(j);
                if (!def.matches(value)) {
                    if (def.isOptional()) {
                        i--; // retry current with next value-definition
                    } else {
                        throw new SchemaValidationException("Value '" + value + "' in property '" + property.getName() + "'does not match any of the expected option(s)" + (def.getOptions() != null ? def.getOptions().toString() : ""));
                    }
                }
                j++;
            }
        }
    }

    public int getMinValueDefinitions() {
        return minValueDefinitions;
    }

    public int getMaxValueDefinitions() {
        return maxValueDefinitions;
    }

    public List<PropertyValueDefinition> getPropertyValueDefinitions() {
        return propertyValueDefinitions;
    }

    private boolean resolvePropertyValueCaseSensitive() {
        return resolveDefinitionPropertyBoolean(PROPERTY_VALUE_CASE_SENSITIVE, true, isPropertyValueCaseSensitive());
    }

    private List<PropertyValueDefinition> resolvePropertyValueDefinitions() {
        List<PropertyValueDefinition> result = new ArrayList<PropertyValueDefinition>();
        for (PropertyGroup group : definition.getPropertyGroups(PROPERTY_VALUE)) {
            result.add(new PropertyValueDefinition(group, settings));
        }
        return result;
    }

    private int resolveMinNumValueDefinitions() {
        int c = 0;
        for (PropertyValueDefinition def : propertyValueDefinitions) {
            if (!def.isOptional()) {
                c++;
            }
        }
        return c;
    }

    private int resolveMaxNumValueDefinitions() {
        return definition.getPropertyGroups(PROPERTY_VALUE).size();
    }

}
